/*
 *  Couleur.cpp
 *  Ray Tracing
 *
 *  Definition des methodes de la classe couleur
 *
 *  Created by Jean-Marc Comby + Romain Dequesne on Sat Mar 20 2004.
 *  Copyright (c) 2004. All rights reserved.
 *
 */

#include "Couleur.hpp"

#include "Utils/borne.hpp"

#include <stdio.h>
#include <cmath>
#include <algorithm>

using namespace std;
using namespace boost::numeric::ublas;

/**
 * Constructeur standard, permet d'initialiser chacune des trois
 * composantes.
 *
 * On fait appel à borne, pour être sur de la conversion de l'argument
 * dans le type désiré.
 *
 * La version par défaut crée du noir (toutes les composantes nulles).
 *
 * @param[in] rouge valeur de la composante rouge.
 * @param[in] vert valeur de la composantes verte.
 * @param[in] bleu valeur de la composantes bleue.
 */
Couleur::Couleur(const int& rouge, const int& vert, const int& bleu)
  : vector< ComposantCouleur >( 3 )
{
  operator()(0) = rouge;
  operator()(1) = vert;
  operator()(2) = bleu;
}

/**
 * Constructeur permettant l'initialisation des trois composantes,
 * cette fois les arguments on directement le bon type.
 *
 * @param[in] rouge valeur de la composante rouge.
 * @param[in] vert valeur de la composantes verte.
 * @param[in] bleu valeur de la composantes bleue.
 */
Couleur::Couleur(const ComposantCouleur& rouge,
		 const ComposantCouleur& vert,
		 const ComposantCouleur& bleu)
  : vector< ComposantCouleur >( 3 )
{
  operator()(0) = rouge;
  operator()(1) = vert;
  operator()(2) = bleu;
}

/**
 * Le destructeur n'a rien de spécial à faire.
 */
Couleur::~Couleur()
{}

/**
 * Constructeur de copie, permet de dupliquer une instance de Couleur.
 *
 * @param[in] c Couleur à copier.
 */
Couleur::Couleur(Couleur const &c)
  : vector< ComposantCouleur >( c )
{}

// 3 methodes pour le retour RVB
/**
 * Permet de récupérer la valeur de la composante rouge.
 *
 * @return une référence constante sur la composante rouge.
 */
const ComposantCouleur& Couleur::rouge() const
{
  return operator()(0);
}

// Retourne la valeur du vert
/**
 * Permet de récupérer la valeur de la composante verte.
 *
 * @return une référence constante sur la composante verte.
 */
const ComposantCouleur& Couleur::vert() const
{
  return operator()(1);
}

// Retourne la valeur du bleu
/**
 * Permet de récupérer la valeur de la composante bleue.
 *
 * @return une référence constante sur la composante bleue.
 */
const ComposantCouleur& Couleur::bleu() const
{
  return operator()(2);
}

/**
 * Permet de récupérer l' \e intensité de la Couleur, qui est utilisée
 * dans la représentation HSL (en français TSL), par opposition à la
 * représentation RGB. L'intensité correspond à la \e luminance (\e
 * lightness).
 *
 * @return la valeur de l'\e intensité.
 */
double Couleur::intensite() const
{
  double min, max,
    norme( static_cast< double >( ComposantCouleur::maxComposante() ) );

  min = static_cast< double >( *min_element( begin(), end() ) ) / norme;
  max = static_cast< double >( *max_element( begin(), end() ) ) / norme;

  return 0.5 * ( max + min );
}

/**
 * Permet de modifier la valeur de la composante rouge.
 *
 * @param[in] rouge nouvelle valeur de la composante rouge.
 */
void Couleur::setRouge(const ComposantCouleur& rouge)
{
  operator()(0) = rouge;
}

/**
 * Permet de modifier la valeur de la composante verte.
 *
 * @param[in] vert nouvelle valeur de la composante verte.
 */
void Couleur::setVert(const ComposantCouleur& vert)
{
  operator()(1) = vert;
}

/**
 * Permet de modifier la valeur de la composante bleue.
 *
 * @param[in] bleu nouvelle valeur de la composante bleue.
 */
void Couleur::setBleu(const ComposantCouleur& bleu)
{
  operator()(2) = bleu;
}

/**
 * Permet de modifier toutes les composantes en un coup.
 *
 * @param[in] rouge nouvelle valeur de la composante rouge.
 * @param[in] vert nouvelle valeur de la composante verte.
 * @param[in] bleu nouvelle valeur de la composante bleue.
 */
void Couleur::setCouleur(const ComposantCouleur& rouge,
			 const ComposantCouleur& vert,
			 const ComposantCouleur& bleu)
{
  setRouge( rouge );
  setVert( vert );
  setBleu( bleu );
}

// Les calculs sur RVB et l'affichage
/**
 * Permet d'afficher la couleur sur la sortie standard, sous le format
 * "(r:valeur v:valeur b:valeur)".
 */
void Couleur::affiche()
{
  cout << "(r:" << operator()(0) << " v:" << operator()(1)
       << " b:" << operator()(2) << ")";
}

// L'addition
/**
 * Permet d'additionner une instance de Couleur à l'instance courante.
 *
 * Il faut faire attention à bien effectuer la saturation, sinon en
 * additionnant deux gris très clairs on peut obtenir du noir.
 *
 * @param[in] coul instance de Couleur à additionner.
 *
 * @return une référence sur l'instance courante modifiée.
 */
Couleur& Couleur::operator+=(const Couleur &coul)
{
  this -> vector< ComposantCouleur >::operator+=( coul );
  // unsigned convert[3], i, valMax( maxComposante() );

  // for (i = 0; i < 3; i++)
  //   {
  //     convert[i]  = operator()(i);
  //     convert[i] += coul.operator()(i);

  //     operator()(i) = static_cast< ComposantCouleur >( borne( convert[i],
  // 						       0u, valMax ) );
  //   }

  return *this;
}

/**
 * Permet d'obtenir le résultat de l'addition de deux instances de
 * Couleur.
 *
 * 
 * @param[in] coul instance de Couleur à additionner.
 *
 * @return une nouvelle instance de Couleur résultant de l'addition de
 * coul et de l'instance courante.
 */
Couleur Couleur::operator+(const Couleur &coul) const
{
  Couleur resultat( *this );

  return resultat += coul;
}

// La soustraction (2 methodes)
/**
 * Permet de soustraire une instance de Couleur à l'instance courante.
 *
 * Il faut faire attention à bien effectuer la saturation, sinon en
 * soustraiyant deux gris très clair on peut obtenir du blanc.
 *
 * @param[in] coul instance de Couleur à soustraire.
 *
 * @return une référence sur l'instance courante modifiée.
 */
Couleur &Couleur::operator-=(const Couleur &coul)
{
  this -> vector< ComposantCouleur >::operator-=( coul );
  // int convert[3], valMax( maxComposante() );
  // unsigned i;
  // for (i = 0; i < 3; i++)
  //   {
  //     convert[i]  = operator()(i);
  //     convert[i] -= coul.operator()(i);

  //     operator()(i) = static_cast< ComposantCouleur >( borne( convert[i],
  // 						       0, valMax ) );
  //   }
  
  return *this;
}

/**
 * Permet d'obtenir le résultat de la soustraction de deux instances
 * de Couleur.
 *
 * 
 * @param[in] coul instance de Couleur à soustraire.
 *
 * @return une nouvelle instance de Couleur résultant de la
 * soustraction de coul et de l'instance courante.
 */
Couleur Couleur::operator-(const Couleur &coul) const
{
  Couleur resultat( *this );

  return resultat -= coul;
}

// Multiplication par un double
/**
 * Permet de multiplier l'instance courante par un nombre réel.
 *
 * Ici aussi il faut faire attention à la saturation.
 *
 * @param[in] a nombre réel par lequel on veut multiplier l'instance
 * courante.
 *
 * @return une référence constante sur l'instance courante modifiée.
 */
Couleur &Couleur::operator*=(const double& a)
{
  this -> vector< ComposantCouleur >::operator*=( a );
  // vector< double > convert( *this );
  // double valMax( maxComposante() );

  // convert *= a;

  // transform( convert.begin(), convert.end(), this -> begin(),
  // 	     bind< double >( borne< double >, _1, 0., valMax )
  // 	     );

  return *this;
}

/**
 * Permet d'obtenir le résultat de l'instance courante multipliée par
 * un nombre réel.
 *
 * Fait appel à *= pour une instance temporaire.
 *
 * @param[in] a nombre réel par lequel on veut multiplier l'instance
 * courante.
 */
Couleur Couleur::operator*(const double& a) const
{
  Couleur resultat( *this );

  return resultat *= a;
}
	
// Comparaison
/**
 * Opérateur d'égalité, permet d'effectuer des comparaisons entre
 * instances de Couleur.
 *
 * @param[in] coul instance que l'on veut comparer avec l'instance
 * courante.
 *
 * @return \e true si les trois composantes coïncident, \e false
 * sinon.
 */
bool Couleur::operator==(const Couleur &coul)
{
  
  return ( operator()(0) == coul.operator()(0) &&
	   operator()(1) == coul.operator()(1) &&
	   operator()(2) == coul.operator()(2) );
}

/**
 * Fonction réalisant l'affichage d'une couleur dans un flot de sortie
 * quelconque. Le format est simplement la valeur des trois
 * composantes à la suite, séparées par des espaces.
 *
 * @param[in] os flot de sortie dans lequel on veut afficher les
 * composantes.
 * @param[in] couleur instance que l'on veut afficher.
 *
 * @return une référence sur le flot de sortie modifié.
 */
ostream& operator<<(ostream &os, const Couleur &couleur)
{
  os << couleur.rouge() << ' ' << couleur.vert() << ' ' << couleur.bleu();

  return os;
}

// Attenuation (borne entre 0 et 1)
/**
 * Réalise l'atténuation d'une Couleur : il s'agit simplement de la
 * multiplication de l'instance par une nombre compris entre 0 et 1.
 *
 * Si l'utilisateur entre un nombre trop grand (ou négatif) il est
 * automatiquement ramené dans l'intervalle [0,1].
 *
 * @param[in] coef coefficient d'atténuation.
 *
 * @return une référence sur l'instance courante modifiée.
 */
Couleur& Couleur::attenue(const double& coef)
{
  return *this *= borne( coef, 0., 1. );
}

// Melange de couleurs
/**
 * Réalise le mélange de deux instances de Couleur, par combinaison
 * convexe :
 *
 * \f[ \alpha \, c_1 + \left( 1 - \alpha \right) \, c_2 \f]
 *
 * On utilise deux fois la méthode *= avec une instance temporaire.
 *
 * @param[in] autre Couleur à combiner avec l'instance courante.
 * @param[in] proportion coefficient appliqué à l'instance courante.
 *
 * @return une référence sur l'instance courante modifiée.
 */
Couleur& Couleur::melange(const Couleur& autre, const double& proportion)
{
	
  Couleur rvb1( autre );
  *this *= borne( proportion, 0., 1. );

  rvb1 *= ( 1. - borne( proportion, 0., 1. ) );

  return *this += rvb1;
}

// Eclaire une couleur
/**
 * Permet d'éclairer une Couleur avec une autre instance.
 *
 * @param[in] lumiere couleur de la lumière éclairant l'instance
 * courante.
 *
 * @return une référence sur l'instance courante modifiée.
 */
Couleur& Couleur::eclairee(const Couleur& lumiere)
{
  double tmp, valMax( ComposantCouleur::maxComposante() );

  double coef( 1. / valMax );

  for (unsigned i(0); i<3; i++)
    {
      tmp = static_cast<double>( lumiere.operator()(i) ) * coef;
      tmp *= static_cast<double>( operator()(i) );
      
      operator()(i) = static_cast<ComposantCouleur>( borne( tmp, 0., valMax ) );
    }

  return *this;
}
